﻿using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using DKE_Kinect.Entwicklung.Core;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using PolyXNAx;

namespace DKE_Kinect.Entwicklung.PolyVox
{
	/// <summary>
	/// Manager für PolyVox bezogene Aktionen zur Volumenbearbeitung und Organisation
	/// </summary>
	public class PolyvoxManager : BaseManager
	{


        private PolyVoxRecord pRecord;
        private PolyXNAxVolume pVolume;
		/// <summary>
		/// Basic Effect als Ausweichmöglichkeit
		/// </summary>
		private BasicEffect effect;
        /// <summary>
        /// Gets or sets the volume.
        /// </summary>
        /// <value>
        /// The volume.
        /// </value>
        public PolyXNAxVolume Volume { get { return pVolume; } set { pVolume = value; } }

        /// <summary>
        /// Gets or sets the volume record.
        /// </summary>
        /// <value>
        /// The volume record.
        /// </value>
        public PolyVoxRecord VolumeRecord { get { return pRecord; } set { pRecord = value; } }
		/// <summary>
		/// Konstruktor. Erstellt eine Instanz des Managers und weist die aktuelle Spielinstanz als Takgeber zu 
		/// </summary>
		/// <param name="game">Aktuelle Spielinstanz</param>
        public PolyvoxManager(Application game)
            : base(game)
        {

            pRecord = new PolyVoxRecord();
            effect = new BasicEffect(pParent.GraphicsDevice);
            pVolume = new PolyXNAxVolume();
            Texture2D colorP= DKE_Kinect.Entwicklung.Core.ContentManager.loadContent<Texture2D>("textures/colorPalette");
           effect.Texture =colorP;
           effect.TextureEnabled = true;


           Voxel.Initialize(colorP);

           
        }
        public void Reset()
        {
            pRecord = new PolyVoxRecord();
            pVolume = new PolyXNAxVolume();
        }
        /// <summary>
        /// Sets the color palette.
        /// </summary>
        /// <param name="palette">The palette.</param>
        public void SetColorPalette(Texture2D palette)
        {
            this.effect.Texture = palette;
        }
        /// <summary>
        /// Sets the voxel at.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="z">The z.</param>

		public void SetVoxelAt (float x, float y, float z)
		{
            pRecord.setVoxel(new Vector3(x,y,z), Voxel.CurrentVoxel,VoxelAction.set);
            pVolume.SetVoxel(new Vector3(x,y,z),Voxel.CurrentVoxel);
		}

        /// <summary>
        /// Sets the voxel at.
        /// </summary>
        /// <param name="position">The position.</param>

        public void SetVoxelAt(Vector3 position)
        {
            
            pRecord.setVoxel(position, Voxel.CurrentVoxel,VoxelAction.set);
            pVolume.SetVoxel(position,Voxel.CurrentVoxel);
        }

        /// <summary>
        /// Sets the sphere at.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="z">The z.</param>
        /// <param name="r">The radius.</param>
        public void SetSphereAt(float x, float y, float z,int r)
        {
            pRecord.setVoxel(new Vector3(x, y, z), Voxel.CurrentVoxel,r,VoxelAction.sphere);
            pVolume.SetSphere(new Vector3(x,y,z),r,Voxel.CurrentVoxel);
        }
        /// <summary>
        /// Sets the sphere at.
        /// </summary>
        /// <param name="pos">The pos.</param>
        /// <param name="r">The radius.</param>
        public void SetSphereAt(Vector3 pos, int r)
        {
            pRecord.setVoxel(pos, Voxel.CurrentVoxel, VoxelAction.sphere);
            pVolume.SetSphere(pos, r, Voxel.CurrentVoxel);
        }


        /// <summary>
        /// Set the block at.
        /// </summary>
        /// <param name="EdgeA">First Corner</param>
        /// <param name="EdgeB">Second Corner</param>

        public void SetBlockAt(Vector3 EdgeA,Vector3 EdgeB)
        {
            pRecord.setVoxel(EdgeA, Voxel.CurrentVoxel, VoxelAction.block);
            pRecord.setVoxel(EdgeB, Voxel.CurrentVoxel, VoxelAction.block);
            pVolume.SetBlock(EdgeA, EdgeB, Voxel.CurrentVoxel);
        }

        /// <summary>
        /// Umsetzung eines Algorithmus zur Rasterung einer Linie in 3D (Prinzip nach Breseham).
        /// Autor: Algorithmus von Jimmy Shen
        /// </summary>
        /// <param name="from">Startpunkt</param>
        /// <param name="to">Endpunkt</param>

		public void DrawLine (Vector3 from, Vector3 to)
        {
            
            #region eriks algo
            /*
            //this.DrawLine(from.X, from.Y, from.Z, to.X, to.Y, to.Z);

			int dx, dy, dz;
			dx =(int) (to.X - from.X);
			dy =(int) (to.Y - from.Y);
			dz =(int) (to.Z - from.Z);

			int d = Math.Max(Math.Abs(dx), Math.Max(Math.Abs(dy), Math.Abs(dz))) + 1;

			int ax, ay, az;
			ax = Math.Abs(dx) * 2;
			ay = Math.Abs(dy) * 2;
			az = Math.Abs(dz) * 2;

			int sx, sy, sz;
			sx = Math.Sign(dx);
			sy = Math.Sign(dz);
			sz = Math.Sign(dy);

			int x, y, z;
			x =(int) from.X;
			y =(int) from.Y;
			z =(int) from.Z;

			//int idx = 1;
			// Stupide umgesetzt...würd ja gerne testen, aber läuft immer noch nich...
			// ===> BITTE TESTEN
			if (ax >= Math.Max(ay, az))
			{
				int yd, zd;
				yd = ay - ax / 2;
				zd = az - ax / 2;

				while (true)
				{
					SetVoxelAt((ushort)x, (ushort)y, (ushort)z);
					//idx++;

					if (x >= to.X)
						break;

					if (yd >= 0)
					{
						y += sy;
						yd -= ax;
					}

					if (zd >= 0)
					{
						z += sz;
						zd -= ax;
					}

					x += sx;
					yd += ay;
					zd += az;
				}
			}
			else if (ay >= Math.Max(ax, az))
			{
				int xd, zd;
				xd = ax - ay / 2;
				zd = az - ay / 2;

				while (true)
				{
					SetVoxelAt((ushort)x, (ushort)y, (ushort)z);
					//idx++;

					if (y >= to.Y)
						break;

					if (xd >= 0)
					{
						x += sx;
						xd -= ay;
					}

					if (zd >= 0)
					{
						z += sz;
						zd -= ay;
					}

					y += sy;
					xd += ax;
					zd += az;
				}
			}
			else
			{
				int xd, yd;
				xd = ax - az / 2;
				yd = ay - az / 2;

				while (true)
				{
					SetVoxelAt((ushort)x, (ushort)y, (ushort)z);
					//idx++;

					if (z >= to.Z)
						break;

					if (xd >= 0)
					{
						x += sx;
						xd -= az;
					}

					if (yd >= 0)
					{
						z += sy;
						yd -= az;
					}

					z += sz;
					xd += ax;
					yd += ay;
				}
            }*/
            #endregion
            
            #region 2terVersuch
        /*    Vector3 toGo=Vector3.Subtract(to, from);
            if (toGo.Length() == 0) return;
            double xWay=toGo.X;
            double yWay = toGo.Y;
            double zWay = toGo.Z;
            double s = Math.Max(Math.Max(Math.Abs(xWay), Math.Abs(yWay)), Math.Abs(zWay));
                xWay /= s;
                yWay /= s;
                zWay /= s;

                for (int i = 1; i <= s; ++i)
                {
                    float x = (float)Math.Round(xWay * i);
                    float y = (float)Math.Round(yWay * i);
                    float z = (float)Math.Round(zWay * i);
                    this.SetVoxelAt(from.X + x, from.Y + y, from.Z + z);
                }
            */
            #endregion

            #region 3terVersuch
            /*
                Vector3 toGo = Vector3.Subtract(to, from);
                if (toGo.Length() == 0) return;
                float xWay = toGo.X;
                float yWay = toGo.Y;
                float zWay = toGo.Z;
                float s = Math.Max(Math.Max(Math.Abs(xWay), Math.Abs(yWay)), Math.Abs(zWay));
                xWay /= s;
                yWay /= s;
                zWay /= s;
         float sx = 0;
         float sy = 0;
         float sz = 0;
         float x = 0;
         float y = 0;
         float z = 0;
         bool nV = true;
                for (int i = 0; i <= s; ++i)
                {
                    if (nV)
                    {
                        this.SetVoxelAt(from.X + sx, from.Y + sy, from.Z + sz);
                        nV = false;
                    }
                    x += xWay;
                    y += yWay;
                    z += zWay;
                    if (x >= 1)
                    {
                        ++sx;
                        --x;
                        nV = true;
                    }
                    if (y >= 1)
                    {
                        ++sy;
                        --y;
                        nV = true;
                    }
                    if (z >= 1)
                    {
                        ++sz;
                        --z;
                        nV = true;
                    }
                    if (x <= -1)
                    {
                        --sx;
                        ++x;
                        nV = true;
                    }
                    if (y <= -1)
                    {
                        --sy;
                        ++y;
                        nV = true;
                    }
                    if (z <= -1)
                    {
                        --sz;
                        ++z;
                        nV = true;
                    }
                }
            */
            #endregion

            #region C++Call
            pRecord.setVoxel(from, Voxel.CurrentVoxel, VoxelAction.line);
            pRecord.setVoxel(to, Voxel.CurrentVoxel, VoxelAction.line); 
            pVolume.DrawLine(from, to, Voxel.CurrentVoxel);
            #endregion
        }

        /// <summary>
        /// Draws the line.
        /// </summary>
        /// <param name="fromX">From X.</param>
        /// <param name="fromY">From Y.</param>
        /// <param name="fromZ">From Z.</param>
        /// <param name="toX">To X.</param>
        /// <param name="toY">To Y.</param>
        /// <param name="toZ">To Z.</param>

        public void DrawLine(float fromX, float fromY, float fromZ, float toX, float toY, float toZ)
		{
            this.DrawLine(new Vector3(fromX, fromY, fromZ), new Vector3(toX, toY, toZ));
		}


		/// <summary>
		/// Draw Zyklus: Wird kontinuirlich getrigger von der Game Klasse. Dient zum zeichnen des Screens
		/// </summary>
		/// <param name="gameTime">Spielzeit Objekt mit Zeit seit letzem Update, Gesamtlaufzeit u.Ä.</param>
		public override void Draw (Microsoft.Xna.Framework.GameTime gameTime)
		{
         
		//	 pParent.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);
			// BasicEffect effect = new BasicEffect(pParent.GraphicsDevice);
		

			effect.EnableDefaultLighting();
			//    effect.World = Matrix.CreateWorld(pPModel.Position, Vector3.Forward, Vector3.Up);
			effect.View = this.pParent.GrafikManager.Camera.ViewMatrix;
			effect.Projection = this.pParent.GrafikManager.Camera.ProjectionMatrix;
            effect.LightingEnabled = true;



            pVolume.Update(this.pParent.GraphicsDevice);   //ERROR: Volume muss größer null sein
            pVolume.Draw(this.pParent.GraphicsDevice, pParent.GrafikManager.Camera.Frustum, effect);
		}

		/// <summary>
		/// Update Zyklus: Wird kontinuirlich von der Game Klasse getriggert um Logik zu berechnen
		/// </summary>
		/// <param name="gameTime">Spielzeit Objekt mit Zeit seit letzem Update, Gesamtlaufzeit u.Ä.</param>
		public override void Update (Microsoft.Xna.Framework.GameTime gameTime)
		{
            this.pVolume.Update(pParent.GraphicsDevice);
          
		}

        /// <summary>
        /// Saves the volume.
        /// </summary>
        /// <param name="name">The name.</param>
        public void SaveVolume(String name)
        {
            BinaryFormatter pBFormatter = new BinaryFormatter();
            Stream pStream = File.Open("../../../../DKE_KinectContent/projects/" + name + ".ppf", FileMode.OpenOrCreate);
            pBFormatter.Serialize(pStream, pRecord);
            pStream.Close();
        }

        /// <summary>
        /// Loads the volume.       
        /// </summary>
        /// <param name="stream">The stream.</param> 
        public void LoadVolume(Stream stream)
        {

            BinaryFormatter pBFormatter = new BinaryFormatter();
        
            try
            {
                pRecord = (PolyVoxRecord)pBFormatter.Deserialize(stream);
            }
            catch (Exception e)
            {
                throw new DkeException("Error", e);
            }
            stream.Close();
        }
	}
}

/*
 * 
 * Matlab - Algorithmus zur Rasterung einer Linie im drei-dimensionalen Raum:
 * 
%  Generate X Y Z coordinates of a 3D Bresenham's line between
%  two given points.
%
%  A very useful application of this algorithm can be found in the
%  implementation of Fischer's Bresenham interpolation method in my
%  another program that can rotate three dimensional image volume
%  with an affine matrix:
%  http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=21080
%
%  Usage: [X Y Z] = bresenham_line3d(P1, P2, [precision]);
%
%  P1	- vector for Point1, where P1 = [x1 y1 z1]
%
%  P2	- vector for Point2, where P2 = [x2 y2 z2]
%
%  precision (optional) - Although according to Bresenham's line
%	algorithm, point coordinates x1 y1 z1 and x2 y2 z2 should
%	be integer numbers, this program extends its limit to all
%	real numbers. If any of them are floating numbers, you
%	should specify how many digits of decimal that you would
%	like to preserve. Be aware that the length of output X Y
%	Z coordinates will increase in 10 times for each decimal
%	digit that you want to preserve. By default, the precision
%	is 0, which means that they will be rounded to the nearest
%	integer.
%
%  X	- a set of x coordinates on Bresenham's line
%
%  Y	- a set of y coordinates on Bresenham's line
%
%  Z	- a set of z coordinates on Bresenham's line
%
%  Therefore, all points in XYZ set (i.e. P(i) = [X(i) Y(i) Z(i)])
%  will constitute the Bresenham's line between P1 and P1.
%
%  Example:
%	P1 = [12 37 6];     P2 = [46 3 35];
%	[X Y Z] = bresenham_line3d(P1, P2);
%	figure; plot3(X,Y,Z,'s','markerface','b');
%
%  This program is ported to MATLAB from:
%
%  B.Pendleton.  line3d - 3D Bresenham's (a 3D line drawing algorithm)
%  ftp://ftp.isc.org/pub/usenet/comp.sources.unix/volume26/line3d, 1992
%
%  Which is also referenced by:
%
%  Fischer, J., A. del Rio (2004).  A Fast Method for Applying Rigid
%  Transformations to Volume Data, WSCG2004 Conference.
%  http://wscg.zcu.cz/wscg2004/Papers_2004_Short/M19.pdf
%
%  - Jimmy Shen (jimmy@rotman-baycrest.on.ca)
%
function [X,Y,Z] = bresenham_line3d(P1, P2, precision)

   if ~exist('precision','var') | isempty(precision) | round(precision) == 0
      precision = 0;
      P1 = round(P1);
      P2 = round(P2);
   else
      precision = round(precision);
      P1 = round(P1*(10^precision));
      P2 = round(P2*(10^precision));
   end

   d = max(abs(P2-P1)+1);
   X = zeros(1, d); !!
   Y = zeros(1, d); !!
   Z = zeros(1, d); !!

   x1 = P1(1);
   y1 = P1(2);
   z1 = P1(3);

   x2 = P2(1);
   y2 = P2(2);
   z2 = P2(3);

   dx = x2 - x1;
   dy = y2 - y1;
   dz = z2 - z1;

   ax = abs(dx)*2;
   ay = abs(dy)*2;
   az = abs(dz)*2;

   sx = sign(dx);
   sy = sign(dy);
   sz = sign(dz);

   x = x1;
   y = y1;
   z = z1;
   idx = 1;

   if(ax>=max(ay,az))			% x dominant
      yd = ay - ax/2;
      zd = az - ax/2;

      while(1)
         X(idx) = x;
         Y(idx) = y;
         Z(idx) = z;
         idx = idx + 1;

         if(x == x2)		% end
            break;
         end

         if(yd >= 0)		% move along y
            y = y + sy;
            yd = yd - ax;
         end

         if(zd >= 0)		% move along z
            z = z + sz;
            zd = zd - ax;
         end

         x  = x  + sx;		% move along x
         yd = yd + ay;
         zd = zd + az;
      end
   elseif(ay>=max(ax,az))		% y dominant
      xd = ax - ay/2;
      zd = az - ay/2;

      while(1)
         X(idx) = x;
         Y(idx) = y;
         Z(idx) = z;
         idx = idx + 1;

         if(y == y2)		% end
            break;
         end

         if(xd >= 0)		% move along x
            x = x + sx;
            xd = xd - ay;
         end

         if(zd >= 0)		% move along z
            z = z + sz;
            zd = zd - ay;
         end

         y  = y  + sy;		% move along y
         xd = xd + ax;
         zd = zd + az;
      end
   elseif(az>=max(ax,ay))		% z dominant
      xd = ax - az/2;
      yd = ay - az/2;

      while(1)
         X(idx) = x;
         Y(idx) = y;
         Z(idx) = z;
         idx = idx + 1;

         if(z == z2)		% end
            break;
         end

         if(xd >= 0)		% move along x
            x = x + sx;
            xd = xd - az;
         end

         if(yd >= 0)		% move along y
            y = y + sy;
            yd = yd - az;
         end

         z  = z  + sz;		% move along z
         xd = xd + ax;
         yd = yd + ay;
      end
   end

   if precision ~= 0
      X = X/(10^precision);
      Y = Y/(10^precision);
      Z = Z/(10^precision);
   end

   return;					% bresenham_line3d

*/